
#include "Display.h"
#include "SDL.h"
#include "SDL_ttf.h"

#include <iostream>
using std::cout;
using std::cerr;
using std::endl;
using std::map;
using std::string;

void Slock(SDL_Surface *screen)
{
  if ( SDL_MUSTLOCK(screen) )
  {
    if ( SDL_LockSurface(screen) < 0 )
    {
      return;
    }
  }
}

void Sulock(SDL_Surface *screen)
{
  if ( SDL_MUSTLOCK(screen) )
  {
    SDL_UnlockSurface(screen);
  }
}

// this is also at http://www.libsdl.org/intro.en/usingvideo.html
void DrawPixel(SDL_Surface *screen, int x, int y,
               Uint8 R, Uint8 G, Uint8 B)
{
  Uint32 color = SDL_MapRGB(screen->format, R, G, B);
  switch (screen->format->BytesPerPixel)
  {
    case 1: // Assuming 8-bpp
    {
      Uint8 *bufp;
      bufp = (Uint8 *)screen->pixels + y*screen->pitch + x;
      *bufp = color;
    }
    break;
    case 2: // Probably 15-bpp or 16-bpp
    {
      Uint16 *bufp;
      bufp = (Uint16 *)screen->pixels + y*screen->pitch/2 + x;
      *bufp = color;
    }
    break;
    case 3: // Slow 24-bpp mode, usually not used
    {
      Uint8 *bufp;
      bufp = (Uint8 *)screen->pixels + y*screen->pitch + x * 3;
      if(SDL_BYTEORDER == SDL_LIL_ENDIAN)
      {
        bufp[0] = color;
        bufp[1] = color >> 8;
        bufp[2] = color >> 16;
      } else {
        bufp[2] = color;
        bufp[1] = color >> 8;
        bufp[0] = color >> 16;
      }
    }
    break;
    case 4: // Probably 32-bpp
    {
      Uint32 *bufp;
      bufp = (Uint32 *)screen->pixels + y*screen->pitch/4 + x;
      *bufp = color;
    }
    break;
  }
}

Display::Display() : 
    mpScreen(NULL), 
    mpCurrentFont(NULL),
    mbScreenLocked(false)
{
  mpScreen=SDL_SetVideoMode(800,600,32,SDL_HWSURFACE|SDL_DOUBLEBUF);
  if ( mpScreen == NULL )
  {
    printf("Unable to set 800x600 video: %s\n", SDL_GetError());
    exit(1);
  }
  // TODO: Do we need to release the screen surface pointer in the destructor?

  cout << "Initializing SDL_ttf." << endl;  
  if(!TTF_WasInit() && TTF_Init()==-1) {
   cout << "Error!  TTF_Init: " << TTF_GetError() << endl;
   exit(2);
  } else {
    cout << "Initialized SDL_ttf correctly." << endl;
  }
  atexit(TTF_Quit);
}

Display::~Display()
{
  mpCurrentFont = NULL;

  // Wipe up all the fonts in the font map
  map<string, map<int,TTF_Font*> >::iterator itFamily = mFontMap.begin();
  while(itFamily != mFontMap.end()) {
    cout << "Cleaning font family '" << itFamily->first << "'" << endl;
    map<int,TTF_Font*>::iterator itFont = itFamily->second.begin();
    while(itFont != itFamily->second.end()) {
      cout << "Cleaning font '" << itFamily->first << "', " << itFont->first << endl;
      TTF_CloseFont(itFont->second);
      itFont->second = NULL;

      ++itFont;
    }
    itFamily->second.clear();
    ++itFamily;
  }
  mFontMap.clear();
}

void Display::SetCurrentFont(const char* sFontFileName, int ptSize)
{
  map<int,TTF_Font*>::iterator itFont = mFontMap[sFontFileName].find(ptSize);
  if(itFont != mFontMap[sFontFileName].end()) {
    mpCurrentFont = itFont->second;
  } else {
    cout << "Font map did not have " << sFontFileName << "," << ptSize << endl;

    TTF_Font* theFont = TTF_OpenFont(sFontFileName, ptSize);
    if(!theFont) {
      cerr << "Error!  Could not initialize font (" << sFontFileName << "): " << TTF_GetError() << endl;
      exit(3);
    }

    cout << "Loaded font (" << sFontFileName << "," << ptSize << ")" << endl;
    mFontMap[sFontFileName][ptSize] = theFont;
    mpCurrentFont = theFont;
  }
}

void Display::DrawText(const char* szText, const SDL_Color& color, 
                      Sint16 _x, Sint16 _y, unsigned long flags) const
{ this->DrawText(mpScreen, szText, color, _x, _y, flags); }

void Display::DrawText(SDL_Surface* destSurf, const char* szText, const SDL_Color& color, 
                        Sint16 _x, Sint16 _y, unsigned long flags) const
{
  // possibly remove some of this error handling, it will slow down the text rendering 
  // if many calls to DrawText() are done per frame...
  if(!destSurf) { 
    cerr << "Error!  Display::DrawText(): screen surface pointer was not set" << endl; 
  }
  if(!szText || strlen(szText) == 0) { return; }

  if(!mpCurrentFont) { 
    cerr << "Error!  Display::DrawText(): Current font was not set in Display object" << endl;
  }

  SDL_Surface* surf = TTF_RenderText_Solid(mpCurrentFont, szText, color);
  if(surf) {
    SDL_Rect rc;  rc.x = _x;  rc.y = _y;

    if(flags & sm_disp_centerh) {
      rc.x -= (surf->w)/2;
    }
    if(flags & sm_disp_centerv) {
      rc.y -= (surf->h)/2;
    }

    if( SDL_BlitSurface(surf, NULL, destSurf, &rc) ) {
      cerr << "Error!  Display::DrawText(): Could not blit text: " << SDL_GetError() << endl;
    }

    SDL_FreeSurface(surf);
  } else {
    cerr << "Error!  Display::DrawText(): Could not render text to surface: " << TTF_GetError() << endl;
  }
}

void Display::LockScreen()
{
  if(!mbScreenLocked) {
    Slock(mpScreen);
    mbScreenLocked = true;
  }
}

void Display::UnlockScreen()
{
  if(mbScreenLocked) {
    Sulock(mpScreen);
    mbScreenLocked = false;
  }
}

void Display::Flip()
{
  SDL_Flip(mpScreen);
}
